package tresp.utils;

import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;
import java.util.logging.Logger;

import robocode.ScannedRobotEvent;
import tresp.bthreads.BThreadType;
import tresp.utils.logging.MyLogger;


/**
 * <b>EnemyTank</b>
 * 
 * <p>
 * 	This class represents an enemy cached tank.
 * </p>
 * 
 * 
 * @author <font color="blue">Maxim Kirilov</font>
 *
 */
public class EnemyTank {

    private static Logger logger = MyLogger.getLogger( EnemyTank.class );
    
    /**Last seen enemy heading in radians*/
    private double lastHeadingRadians;
    
    /**Last seen enemy bearing in radians*/
    private double targetBearingRadians;
    
    /**Enemy velocity samples*/
    private double[] velocitySamples = new double[100000];
    
    /**Enemy headings samples*/
    private double[] headingSamples = new double[100000];
    
    /**Enemy positions samples*/
    private Point2D[] positionSamples = new Point2D[100000];
    
    /**Represents the symbolic pattern convertion*/
    private StringBuffer pattern = new StringBuffer();
    
    /**Fire Power*/
    private double power = 1.5D;
    
    /**Last measured distance from the enemy*/
    private double currentDistanceFromEnemy;
    
    
    private int shots = 1;
    
    private int hits = 1;
    
    
    private Vector<String> cookedpatterns = new Vector<String>();
    
    private double[] targetBearings;
    
    
    /**Samples index*/
    private int sampleIndex;
    
    private double predbearing;
    
    /**Pattern analyzer*/
    private PatternAnalyzer patternAnalyzer;
    
    /**Tree that stores the patterns*/
    private SymbolicPatternNode patterntree;
    
    public EnemyTank(String name){
	
	logger.info("Creating Enemy Tank: "+ name);
	
	patterntree = new SymbolicPatternNode("**");
	
    }
    

    
    
    /**
     * 
     * @param e
     * @param tankInfo
     * @return
     */
    public List<BThreadVote> recommend(ScannedRobotEvent e, ITankInfo tankInfo, String cokedpattern){
	targetBearingRadians = e.getBearingRadians() + tankInfo.getHeadingRadians();
	
	int nbearings = 0;
	if (tankInfo.getGunHeat() < 0.5D) {
	    Vector<Integer> matches = this.patterntree.getMatches(cokedpattern, Math.min(Math.max(tankInfo.getRoundNum(), 1), 20));
	    double currX = tankInfo.getX(); 
	    double currY = tankInfo.getY();
	    for (int i = matches == null ? -1 : matches.size() - 1; (i >= 0) && (nbearings < 100); i--) {
		int match = ((Integer)matches.get(i)).intValue();
		if (match <= sampleIndex - 100) {
		    double b = calculateBearing(match, currX, currY);
		    if (Double.isNaN(b)){ continue; }
		    this.targetBearings[(nbearings++)] = normalRelativeAngle(b - targetBearingRadians);
		}
	    }
	    Arrays.sort(this.targetBearings, 0, nbearings);
	    predbearing = centersample2(this.targetBearings, nbearings);
	} else {
	    predbearing = 0.0D;
	}
	
	
	List<BThreadVote> lRecommendations = new ArrayList<BThreadVote>();
	
	BThreadVote vote = new BThreadVote(BThreadType.TURN_GUN_RIGHT_RADIANS, 0D, 
		normalRelativeAngle(predbearing + targetBearingRadians - tankInfo.getGunHeadingRadians()));
	lRecommendations.add( vote );
	
	
	if (tankInfo.getEnergy() > power + 0.1D){ 
	    power = power(e, tankInfo);
	    
	    if ((Math.abs( tankInfo.getGunTurnRemaining() ) < 10) && (tankInfo.getGunHeat() == 0)){
		vote = new BThreadVote(BThreadType.FIRE, 0D, power);
		lRecommendations.add( vote );
		shots++;
	    }

	}
	if (tankInfo.getEnergy() <= 3){
	    power = 0.2D;
	}
	
	
	return lRecommendations;
    }


    public void updateHitCounter(){
	hits++;
    }

    private double power(ScannedRobotEvent e, ITankInfo tankInfo) {
	if (1.0D * hits / (1.0D * shots) > 0.3D)
	    return 3.0D;
	return Math.min(Math.min(e.getEnergy() / 4.0D, tankInfo.getEnergy() / (e.getDistance() >= 130.0D ? 5 : 1)), 1.9D);
    }
    public String updateEnemy(ScannedRobotEvent e, ITankInfo tankInfo) {
	
	headingSamples[sampleIndex] = normalRelativeAngle(e.getHeadingRadians() - lastHeadingRadians);
	lastHeadingRadians = e.getHeadingRadians();
	velocitySamples[sampleIndex] = e.getVelocity();
	currentDistanceFromEnemy = e.getDistance();
	positionSamples[sampleIndex] = new Point2D.Double(
						tankInfo.getX() + Math.sin(targetBearingRadians) * currentDistanceFromEnemy, 
						tankInfo.getY() + Math.cos(targetBearingRadians) * currentDistanceFromEnemy);

	int pointer = (int)(e.getVelocity() + 10.0D); 
	pattern.append((char)pointer);

	pointer = (int)(e.getVelocity() * Math.sin(e.getHeadingRadians() - targetBearingRadians) + 10.0D);
	pattern.append((char)pointer);

	String cokedpattern = pattern.substring(Math.max(pattern.length() - 60, 0), pattern.length()) + "**";
	cookedpatterns.add(cokedpattern);
	if (sampleIndex >= 30) {
	    String delayedcookedpattern = cookedpatterns.get(sampleIndex - 30);
	    this.patterntree.addSequenceAsynchronous(patternAnalyzer, delayedcookedpattern, sampleIndex - 30);
	}
	sampleIndex += 1;
	return cokedpattern;
    }
    
    private final double normalRelativeAngle(double angle) {
	
	return (angle + 15.707963267948966D) % 6.283185307179586D - 3.141592653589793D;
    }
    
    public double calculateBearing(int match, double currX, double currY)
    {
	match++;

	double x = Math.sin(targetBearingRadians) * currentDistanceFromEnemy;
	double y = Math.cos(targetBearingRadians) * currentDistanceFromEnemy;
	double ah = lastHeadingRadians;
	int time = match;
	boolean hitwall = false;

	while (((match - time + 2) * (20.0D - 3 * power) < Point2D.distance(0.0D, 0.0D, x, y)) && (!hitwall)) {
	    x += Math.sin(ah) * velocitySamples[match];
	    y += Math.cos(ah) * velocitySamples[match];
	    ah += headingSamples[match];
	    match++;
	    if ((x + currX <= 790.0D) && (x + currX >= 10.0D) && (y + currY <= 590.0D) && (y + currY >= 10.0D)){ 
		continue; 
	    }
	    hitwall = true;
	}
	if (!hitwall) {
	    return Math.atan2(x, y);
	}
	return (0.0D / 0.0D);
    }
    
    
    public void stopPatternAnalyzer(){
	patternAnalyzer.stopAnalyzer();
	patternAnalyzer = null;
    }
    
    public void startPatternAnalyzer(){
	targetBearings = new double[100];
	patternAnalyzer = new PatternAnalyzer();
	ThreadManager.getInstance().submit(" Pattern Analyzer", patternAnalyzer );
    }
    
    public boolean isPatternAnalyzerActive(){
	return patternAnalyzer != null;
    }
    
    
    private final double centersample2(double[] val, int nbearings) {
	if (nbearings == 0){ return 0.0D; }
	if (nbearings <= 2) { return val[0]; }
	
	double width = Math.abs(Math.atan(0.0D));
	double maxscore = 0.0D; 
	double maxval = 0.0D;
	for (int i = 0; i < nbearings; i++) {
	    double count = 0.0D;
	    for (int j = i; (j >= 0) && (Math.abs(val[i] - val[j]) < width); count += 1.0D){ 
		j--;
	    }
	    for (int j = i + 1; (j < nbearings) && (Math.abs(val[i] - val[j]) < width); count += 1.0D){
		j++;
	    }
	    if ((count <= maxscore) && ((count != maxscore) || (Math.abs(val[i]) >= Math.abs(maxval)))){
		continue; 
	    }
	    maxscore = count; 
	    maxval = val[i];
	}
	return maxval;
    }
}
